#※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
# File Name: date.sh
# Author: GID5564
# Description:
# Created Time: 06/11/23-20:18:14
#※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※※
#! /usr/bin/env bash


# 避免重复导入
#[[ -n $__XLIB_IMPORTED__DATE ]] && return 0
#__XLIB_IMPORTED__DATE=1

function __date_init__() {
    export DEFAULT_DATE_FORMAT="%Y-%m-%d %a"
    export DEFAULT_DATE_TIME_FORMAT="%Y-%m-%d %H:%M:%S"
    export __START_WATCH=""
}

########################################
# 描述: 时间格式化
# 参数: $1,日期时间 $2,格式
# 返回: 
# 实例: format <source date> <format>
########################################
function format() {
    local tdate=$1
    local format=$2

    # 检查 format 是否合法
    if [[ ! $format =~ ^[%Y-%m-%d %H:%M:%S]+$ ]]; then
        echo "Invalid date format"
        return 1
    fi

    # 使用 -d 选项解析 tdate
    printf "$(date +"$format" -d "$tdate")"
}

########################################
# 描述: 获取当前时间
# 参数: 
# 返回: 当前时间
# 实例: now
########################################
function now() {
    echo $(date +"$DEFAULT_DATE_TIME_FORMAT")
}
########################################
# 描述: 获取当前日期
# 参数: 
# 返回: 当前日期
# 实例: today
########################################
function today() {
    echo "$(date +"$DEFAULT_DATE_FORMAT")"
}
########################################
# 描述: 获取当前时间戳(秒)
# 参数: 
# 返回: 当前时间戳
# 实例: timestamp
########################################
function timestamp() {
    echo $(date +%s)
}
########################################
# 描述: 获取当前时间戳(纳秒)
# 参数: 
# 返回: 当前时间戳
# 实例: mtimestamp
########################################
# Timestamp In Milliseconds
function mtimestamp() {
    echo '('$(date +"%s.%N") ' * 1000000)/1' | bc
}
########################################
# 描述: 获取时间差(秒)
# 参数: $1,开始时间戳 $2,结束时间戳
# 返回: 时间差
# 实例: elapsed <start> <stop>
########################################
function elapsed() {
    local START="$1"
    local STOP="$2"

    local ELAPSED=$(( STOP - START ))
    echo $ELAPSED
}
########################################
# 描述: 计算两个时间点之间的差值(纳秒)
# 参数: 
# 返回: 差值
# 实例: time_timer_start
#       time_timer_get_elapsed
########################################
# 计算两个时间点之间的差值
time_timer_start_time=""
time_timer_start() {
    time_timer_start_time=$(date +%s%N)
}
time_timer_get_elapsed() {
    local end_time="$(date +%s%N)"
    local elapsed_time_in_ns=$(($end_time - $time_timer_start_time))
    local elapsed_time_in_ms=$(($elapsed_time_in_ns / 1000000))
    echo "$elapsed_time_in_ms"
}
########################################
# 描述: 超时检测($?)
# 参数: 
# 返回: 
# 实例: start_watch
#       stop_watch
########################################
start_watch() {
    __START_WATCH=$(now)
}

stop_watch() {
    if has_value __START_WATCH; then
        STOP_WATCH=$(now)
        elapsed "$__START_WATCH" "$STOP_WATCH"
        return 0
    else
        return 1
    fi
}

########################################
# 描述: 字符串转换为日期
# 参数: $1,字符串
# 返回: 日期
# 实例: toDate <str>
########################################
function toDate() {
    echo "$(date -d @$1)"
}
########################################
# 描述: 日期字符串转换为时间戳(秒)
# 参数: 
# 返回: 时间戳
# 实例: toTimestamp <time>
#   如: time:'2024/7/7 15:45:24'
########################################
function toTimestamp() {
    date -d "$1" +"%s"
}


########################################
# 描述: 日期的加减，根据 <type> 对今日日期增加 <number>，可为负值
# 参数: $1,时间类型(年、月、日、星期)
#       $2,加减的天数
# 返回: 日期
# 实例: # getDays <type> <number>
########################################
function getDays() {
    local type=$(echo "$1" | tr '[:lower:]' '[:upper:]')
    local number=${2:-0}

    local type_str
    case "$type" in
    D | DAY | DAYS)
        type_str="days"
        ;;
    M | MONTH | MONTHS)
        type_str="months"
        ;;
    W | WEEK | WEEKS)
        type_str="weeks"
        ;;
    Y | YEAR | YEARS)
        type_str="years"
        ;;
    esac

    # date --date="2 days ago" 和 --date="-2 days" 是相同的，
    # 因此此处不判断运算类型
    echo "$(date --date="$number $type_str")"
}

########################################
# 描述: 函数用于将给定的日期字符串转换为 Unix 时间戳(秒)
# 参数: $1,日期字符串
# 返回: 时间戳
# 实例: date2stamp <time>
#   如: date2stamp '2024/7/7 15:45:24'
########################################
function date2stamp() {
    # 检查是否有输入参数
    if [ -z "$1" ]; then
        echo "Error: No date string provided."
        return 1
    fi

    # 尝试转换日期字符串为 Unix 时间戳
    timestamp=$(date --utc --date "$1" +%s 2>&1)

    # 检查 date 命令是否成功执行
    if [ $? -ne 0 ]; then
        echo "Error: Invalid date string. Please provide a valid date in the format 'YYYY-MM-DD'."
        return 1
    fi

    # 输出 Unix 时间戳
    echo "$timestamp"
}
########################################
# 描述: 使用date命令将给定的时间戳转换为UTC日期和时间
# 参数: $1,时间戳
# 返回: 日期字符串
# 实例: stamp2date <stamp>
#   如: stamp2date '1628753245'
########################################
function stamp2date() {
    # --utc选项表示使用协调世界时
    # --date选项后面跟着一个格式化字符串，其中$1是传递给函数的第一个参数（时间戳）
    # "+%Y-%m-%d %T"表示输出格式为：年-月-日 时：分：秒
    date --utc --date "$1" "+%Y-%m-%d %T"
}

# -s: sec. | -m: min. | -h: hours  | -d: days (default)
########################################
# 描述: 计算两个日期字符串间的差
# 参数: $1,输出类型(s、m、h、d)
#       $2,日期字符串
#       $3,日期字符串
# 返回: 差值
# 实例: # dateDiff <type> <time1> <time2>
########################################
function dateDiff() {
    case $1 in
    -s)
        sec=1
        shift
        ;;
    -m)
        sec=60
        shift
        ;;
    -h)
        sec=3600
        shift
        ;;
    -d)
        sec=86400
        shift
        ;;
    *) sec=86400 ;;
    esac
    dte1=$(date2stamp $1)
    dte2=$(date2stamp $2)
    diffSec=$((dte2 - dte1))
    if ((diffSec < 0)); then abs=-1; else abs=1; fi
    echo $((diffSec / sec * abs))
}
# leap <year>
# 判断是否是闰年
########################################
# 描述: 判断是否是闰年
# 参数: $1,年份
# 返回: 0,是闰年 
#       1,不是闰年
# 实例: IsLeap <year>
########################################
function IsLeap() {
    # based on http://cfajohnson.com/shell/date-functions/?is_leap_year

    local year="$1"
    local gregorian=1752

    [[ -z $year ]] && printf -v year '%(%Y)T'

    if ((year > gregorian)); then
        case $year in
        *0[48] | \
            *[2468][048] | \
            *[13579][26] | \
            *[02468][048]00 | \
            *[13579][26]00)
            return 0
            ;;
        *)
            return 1
            ;;
        esac
    else
        ((year % 4 == 0))
    fi
}

function is_leap_year() {
    year=$1
    if ((year % 4 == 0 && year % 100 != 0)) || ((year % 400 == 0)); then
        return 0
    else
        return 1
    fi
}

########################################
# 描述: 将给定的时间戳转换为指定深度的路径格式
# 参数: $1,时间戳 $2,深度(y、m、d、h、m、s)
# 返回: 路径
# 实例: to_path <stamp> <depth>
########################################
function to_path() {
    local timestamp="${1:-$(date +%s)}" depth="${2:-day}"
    local fmt

    case "$depth" in
    year)
        fmt="%Y"
        ;;
    month)
        fmt="%Y/%m"
        ;;
    day)
        fmt="%Y/%m/%d"
        ;;
    hour)
        fmt="%Y/%m/%d/%H"
        ;;
    minute)
        fmt="%Y/%m/%d/%H/%M"
        ;;
    second)
        fmt="%Y/%m/%d/%H/%M/%S"
        ;;
    *)
        echo "Invalid depth specified!"
        return 1
        ;;
    esac

    date -d@"$timestamp" +"$fmt"
}

# days <month> <year>
# 返回该月所包含的天数
########################################
# 描述: 该月所包含的天数
# 参数: $1,月份 $2,年份
# 返回: 天数
# 实例: days <month> <year>
########################################
function days() {
    # based on http://cfajohnson.com/shell/date-functions/?days_in_month

    declare -i month="${1#0}"
    declare -i year="${2:-$(date +%Y)}"
    declare -i days_in_month

    case $month in

    9 | 4 | 6 | 11)
        days_in_month=30
        ;;
    1 | 3 | 5 | 7 | 8 | 10 | 12)
        days_in_month=31
        ;;
    2)
        #               February alone
        # Which has but twenty-eight
        # Or twenty-nine each leap year
        year date::is-leap "$year" &&
            days_in_month=29 ||
            days_in_month=28
        ;;
    *)
        return 5
        ;;
    esac

    echo $days_in_month
}
########################################
# 描述: 给定MonthName或其缩写，返回其索引
# 参数: $1,英文月名或其缩写
# 返回: 
# 实例: month_number <month name>
########################################
function month_number() {
  declare -A month_map=(
    [january]=1 [jan]=1 [ja]=1
    [february]=2 [feb]=2 [fe]=2
    [march]=3 [mar]=3 [ma]=3
    [april]=4 [apr]=4 [ap]=4
    [may]=5 [ma]=5
    [june]=6 [jun]=6 [ju]=6
    [july]=7 [jul]=7
    [august]=8 [aug]=8 [au]=8
    [september]=9 [sep]=9 [se]=9
    [october]=10 [oct]=10 [oc]=10
    [november]=11 [nov]=11 [no]=11
    [december]=12 [dec]=12 [de]=12
  )

  local mon="$(echo "$1" | tr 'A-Z' 'a-z')"
  if [[ ${month_map[$mon]} ]]; then
    echo "${month_map[$mon]}"
  else
    echo "Error: Bad month name: $1" >&2
    return 1
  fi
}

########################################
# 描述: 将日期转换为儒略日数
# 参数: $1,年$2,月,$3,日
# 返回: 儒略日数
# 实例: date_to_jdays <year> <month> <day>
########################################
function date_to_jdays() {
  local year=$1
  local month=$2
  local day=$3
  local a=(0 3 2 5 0 3 5 1 4 6 2 4)
  local week=$(( (year - 1) / 4 - (year - 1) / 100 + (year - 1) / 400 + a[month-1] + day))
  if [[ ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)) && (month < 3) ]]; then
    week=$((week - 1))
  fi
  echo $((week + 1))
}

# 定义 week_number 函数，计算给定日期的周数
########################################
# 描述: 计算给定日期的周数
# 参数: $1,年$2,月,$3,日
# 返回: 周数
# 实例: week_number <year> <month> <day>
########################################
function week_number() {
  local year=$1
  local month=$2
  local day=$3
  local jdays=$(date_to_jdays $year $month $day)
  local d4=$(( ( ( ( jdays + 31741 - (jdays % 7) ) % 146097) % 36524) % 1461 ))
  local l=$(( d4 / 1460 ))
  local d1=$(( ( (d4 - l) % 365 ) + l ))
  local week_no=$(( ( d1 / 7 ) + 1 ))
  echo $week_no
}


########################################
# 描述: 帮助
# 参数: 
# 返回: 
# 实例: date_help
########################################
function date_help() {
    cat <<EOD
            Format/result           |       Command              |          Output
    --------------------------------+----------------------------+------------------------------
    YYYY-MM-DD_hh:mm:ss             | date +%F_%T                | $(date +%F_%T)
    YYYYMMDD_hhmmss                 | date +%Y%m%d_%H%M%S        | $(date +%Y%m%d_%H%M%S)
    YYYYMMDD_hhmmss (UTC version)   | date --utc +%Y%m%d_%H%M%SZ | $(date --utc +%Y%m%d_%H%M%SZ)
    YYYYMMDD_hhmmss (with local TZ) | date +%Y%m%d_%H%M%S%Z      | $(date +%Y%m%d_%H%M%S%Z)
    YYYYMMSShhmmss                  | date +%Y%m%d%H%M%S         | $(date +%Y%m%d%H%M%S)
    YYYYMMSShhmmssnnnnnnnnn         | date +%Y%m%d%H%M%S%N       | $(date +%Y%m%d%H%M%S%N)
    YYMMDD_hhmmss                   | date +%y%m%d_%H%M%S        | $(date +%y%m%d_%H%M%S)
    Seconds since UNIX epoch:       | date +%s                   | $(date +%s)
    Nanoseconds only:               | date +%N                   | $(date +%N)
    Nanoseconds since UNIX epoch:   | date +%s%N                 | $(date +%s%N)
    Z-notation UTC timestamp        | date --utc +%FT%TZ         | $(date --utc +%FT%TZ)
    Z-notation UTC timestamp + ms   | date --utc +%FT%T.%3NZ     | $(date --utc +%FT%T.%3NZ)
    ISO8601 UTC timestamp           | date --utc +%FT%T%Z        | $(date --utc +%FT%T%Z)
    ISO8601 UTC timestamp + ms      | date --utc +%FT%T.%3N%Z    | $(date --utc +%FT%T.%3N%Z)
    ISO8601 Local TZ timestamp      | date +%FT%T%Z              | $(date +%FT%T%Z)
    YYYY-MM-DD (Short day)          | date +%F\(%a\)             | $(date +%F\(%a\))
    YYYY-MM-DD (Long day)           | date +%F\(%A\)             | $(date +%F\(%A\))
    --------------------------------+----------------------------+------------------------------
    # %a 简称，例如 Sun,Mon
    # %A 星期的全称，例如 Sunday,Monday 等

    #%b 月份的简称，例如 Jan,Feb 等
    #%B 月份的全称，例如 January,February 等

    #%c 日期和时间，例如 Sat 29 Oct 2020 05:02:25 PM CST
    #%C 世纪
    #%d 当月的第几天
    #%D 日期，相当于 %m/%d/%y
    #%e 当月的第几天，用空格代替首位的0，相当于 %_d
    #%F 日期，相当于 %Y-%m-%d
    #%g 年份的后两位
    #%G 四位年份
    #%h 相当于 %b
    #%H 小时，从00到23
    #%I 小时，从01到12
    #%j 当年的第几天，001到366之间
    #%k 小时，用空格代替首位的0，相当于 %_H
    #%l 小时，用空格代替首位的0，相当于 %_I
    #%m 月份，从01到12
    #%M 分钟，从00到59
    #%n 换行
    #%N 纳秒，从000000000到999999999
    #%p 上午(AM)或下午(PM)
    #%P 类似月%p，但是为小写
    #%q 一年的四分之几，1到4
    #%r 12小时制时间，例如 11:11:04 PM
    #%R 24小时制时间，相当于%H:%M
    #%s 从 1970-01-01 00:00:00 UTC 到当前的秒数
    #%S second (00..60)
    #%t tab符
    #%T 时间，相当于 %H:%M:%S
    #%u 星期几，从1到7，1表示星期一
    #%U 一年的第几个星期，以星期六作为星期的开始，00到53
    #%V ISO星期数, 以星期一作为星期的开始，01到53
    #%w 星期几(0..6); 0 表示星期六
    #%W ISO星期数, 以星期一作为星期的开始，00到53
    #%x 日期，例如 12/31/99
    #%X 时间，例如 23:13:48
    #%y 年份的后两位(00..99)
    #%Y 四位年份
    #%z 时区，例如 -0400
    #%:z 时区，例如 -04:00
    #%::z 时区，例如 -04:00:00
    #%:::z 时区，例如 -04, +05:30
    #%Z 时区, 例如 EDT

    # 日期/时间加减的几个例子：
    #date --date "$dte  2 days 1 hour 5 sec"
    #date --date "$dte 3 days 5 hours 10 sec ago"
    #date --date "$dte -3 days -5 hours -10 sec"
    #date --date "$dte 3 days 5 hours 10 sec ago"
    #date --date "$dte -3 days -5 hours -10 sec"

EOD
}

__date_init__
